pub struct Uniform<T: bytemuck::Pod> {
    data_type: std::marker::PhantomData<T>,
    pub buffer: wgpu::Buffer,
    pub bind_group: wgpu::BindGroup
}

impl<T: bytemuck::Pod> Uniform<T> {
    pub fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, data: &T, buffer_label: &str, bind_group_label: &str) -> Self {
        use wgpu::util::DeviceExt;

        let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some(buffer_label),
            contents: bytemuck::cast_slice(&[*data]),
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
        });

        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: buffer.as_entire_binding()
                }
            ],
            label: Some(bind_group_label)
        });

        Self {
            data_type: std::marker::PhantomData,
            buffer,
            bind_group
        }
    }

    pub fn update(&self, queue: &wgpu::Queue, data: &T) {
        queue.write_buffer(
            &self.buffer,
            0,
            bytemuck::cast_slice(&[*data])
        )
    }
}

impl Uniform<()> {
    pub fn bind_group_layout(device: &wgpu::Device, visibility: wgpu::ShaderStages, label: &str) -> wgpu::BindGroupLayout {
        device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None
                    },
                    count: None
                }
            ],
            label: Some(label)
        })
    }
}
